#include <QTime>
#include "clientthread.h"
#include "server.h"



ClientThread::ClientThread(Server *s, int descriptor) :
    disconnecting(false)
{
    this->descriptor = descriptor;
    serv = s;
    _socket = 0;
    current_upload_speed = 0;
    current_download_speed = 0;
    total_uploaded = 0;
    total_downloaded = 0;
    disconnect_emited = false;
    force_exit = false;

    transfer.last_size = 0;
    transfer._sz = 0;
    transfer.file_fd = -1;
}


ClientThread::~ClientThread()
{
    qWarning() << __FUNCTION__ << "trying to kill";

    if(_socket && _socket->isOpen()) {
        if(isRunning())
            emit closeSocket();
        else
            _socket->close();
    }

    qWarning() << "close";

    //if(isRunning() && !disconnect_emited) {
        if(!force_exit) {
            //wait_cond.wait(&mtx);
        }
    //}

    if(_socket) {
        if(_socket->isOpen())
            _socket->waitForDisconnected(4000);
        delete _socket;

        qWarning() << "delete";
    }


    qWarning() << "wait start";
    wait();
    qWarning() << "wait end";


    _socket = 0;
    qWarning() << "Thread(" << _ip << ") Destroyed";
}


void ClientThread::abort()
{
    qWarning() << "ClientThread Aborting!";

    if(_socket) {
        disconnecting = true;
        emit socketDisconnectFromHost();
    }
}



void ClientDisc(ClientThread *th)
{
    th->clientDisconnected();
}

void ClientThread::force_abort()
{
    qWarning() << "ClientThread Force Aborting!";

    force_exit = true;
    mtx.unlock();

    disconnecting = true;
    if(_socket)
        emit socketDisconnectFromHost();

    qWarning() << "Terminate";
    emit exitThread();

    qWarning() << "waitForFinished";

    if(isRunning())
        wait();

    qWarning() << "Force abort finished";

    //clientDisconnected();
    if(!disconnect_emited)
        serv->scheduler.schedule((void*)ClientDisc, (void*)this);
}



QString ClientThread::request_location(const QString & head)
{
    QString s;

    int from = head.indexOf(" ");
    if(from == -1)
        return s;

    from ++;

    int i = head.indexOf(" ", from);
    if(i > -1)
        s = head.mid(from, i-from);
    return s;
}



int ClientThread::prepareAnsverData(const QString & head, QString & ansver, VirtualFsObject **vfs_obj, int *fd)
{
    QString t;
    *vfs_obj = 0;
    *fd = -1;

    /* getting data */
    if( head.startsWith("GET") )
    {
        /* what we need to transfer? */
        t = request_location(head);
        if(t.isEmpty()) {
            qWarning() << "Error while take location";
            return -1;
        }

        t = trUtf8(QByteArray::fromPercentEncoding(t.toUtf8()));
        /* we need transfer a tree of files! */
        if( t.endsWith("/") )
        {
            /* generating page for view a tree */
            ansver = serv->pgen.genHtmlForFolder(t);
            return 0;
        }
        /* need the data? */
        else
        {
            /* try get a object by url */
            VirtualFsObject *vfso = serv->vfs.file(t);
            *vfs_obj = vfso;
            /* object found */
            if(vfso)
            {
                *fd = vfso->fileOpen();
                if(*fd == -1)
                {
                    /* generate a error */
                    ansver = serv->pgen.genError(QString("Error: File not found on server %1").arg(t));
                    return 0;
                } else if(vfso->fileSize(*fd) < 1) {

                    ansver = serv->pgen.genError(QString("Error: File has zero length %1").arg(t));
                    return 0;
                } else {

                    _header = QHttpRequestHeader(head);

                    QString range = _header.value("Range");

                    if(range.startsWith("bytes="))
                    {
                        ansver = "HTTP/1.1 206 Partial Content\r\n";

                        int pos = range.indexOf("-");

                        QString start_size = range.mid(6, pos-6);
                        quint64 start_sizei = start_size.toULongLong();
                        qWarning() << "Start size: " << start_size;

                        QString end_size = range.mid(pos+1);
                        quint64 end_sizei = 0;
                        qWarning() << "End size: " << end_size;

                        //QString all_size = QString::number(vfso->fileSize(*fd));
                        quint64 all_sizei = vfso->fileSize(*fd);

                        if(end_size.isEmpty()) {
                            end_size = QString::number(all_sizei);
                            end_sizei = vfso->fileSize(*fd);
                        }
                        else {
                            /* если у нас ендсайз больше размера */
                            if((quint64)vfso->fileSize(*fd) < end_sizei)
                            {
                                //416 Requested Range Not Satisfiable
                                ansver = "HTTP/1.1 416 Requested Range Not Satisfiable\r\n";
                                goto end;
                            }
                            end_size = QString::number(end_sizei);
                        }

                        vfso->fileSeek(*fd, start_sizei);
                        ansver += QString("Content-Range: bytes %1-%2/%3\r\n").arg(start_size).arg(end_size).arg(all_sizei);
                        ansver += QString("Content-Length: %1\r\n").arg(end_sizei - start_sizei);
                    } else {

                        ansver = "HTTP/1.1 200 OK\r\n";
                        ansver += QString("Content-Length: %1\r\n").arg(vfso->fileSize(*fd));
                    }

                    end:
                    ansver += "Accept-Ranges: bytes\r\n";
                    ansver += "Connection: keep-alive\r\n";
                    ansver += "Server: MyDesktopServer (c) Z.Vova\r\n\r\n";

                    return 0;
                }
            } else { /* if(vfso) */

                ansver = serv->pgen.genError(QString("Error: File not found or permission denied"));
                return 0;
            }
        }
    }
    return 0;
}


inline int average(int last, int _new)
{
    if(_new > last)
        return last + (_new-last)/2;

    return last + (last - _new)/2;
}


void ClientThread::transferData()
{
    /* create socket */
    _socket = new QTcpSocket();

    connect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(socket_bytes_written(qint64)), Qt::DirectConnection);
    connect(_socket, SIGNAL(readyRead()), this, SLOT(socket_ready_read()), Qt::DirectConnection);
    connect(_socket, SIGNAL(disconnected()), this, SLOT(socket_disconnected()), Qt::DirectConnection);
    connect(this, SIGNAL(exitThread()), this, SLOT(quit()));
    connect(this, SIGNAL(closeSocket()), this, SLOT(close_socket()));
    connect(this, SIGNAL(socketDisconnectFromHost()), _socket, SLOT(disconnectFromHostImplementation()));
    connect(this, SIGNAL(finished()), this, SLOT(thread_finished()), Qt::DirectConnection);

    /* set descriptor */
    _socket->setSocketDescriptor(descriptor);
    _socket->waitForConnected();
    clientConnected();

    QThread::exec();
}



void ClientThread::close_socket()
{
    qWarning() << "close_socket!!!!!!";
    if(_socket)
        _socket->close();
}


void ClientThread::thread_finished()
{
    //clientDisconnected();
    wait_cond.wakeOne();
}


void ClientThread::socket_disconnected()
{
    qWarning() << __FUNCTION__;
    setDownloadingObject("");

    if(transfer.fs_obj && transfer.file_fd > -1)
        transfer.fs_obj->fileClose(transfer.file_fd);
    else
        qWarning() << "File not opened, nothing to do";

    transfer.file_fd = -1;

    current_upload_speed = 0;
    current_download_speed = 0;

    _socket->close();
    clientDisconnected();

    this->quit();
}


void ClientThread::socket_bytes_written(qint64 bytes)
{
    qWarning() << __FUNCTION__ << bytes ;
    socket_bytes_write();
}


void ClientThread::socket_bytes_write()
{
    qint64 fsize;
    while(!disconnecting && transfer.file_fd > -1 && (qint64)transfer.uploaded < (fsize = transfer.fs_obj->fileSize(transfer.file_fd))) {

        if((qint64)transfer.uploaded + (qint64)transfer.part_size > fsize)
            transfer.part_size = transfer.uploaded + transfer.part_size - fsize;

        transfer.send_data = transfer.fs_obj->fileRead(transfer.file_fd, transfer.part_size);
        if(transfer.send_data.size() < 1) {
            qWarning() << "fs_obj->fileRead(): Cannot read the data!";
            break;
        }

        transfer._sz = _socket->write(transfer.send_data);
        if(transfer._sz < 1) {
            qWarning() << "write(): Data send is break!";
            break;
        }

        transfer.last_size += transfer._sz;

        quint32 sec = transfer.time.elapsed()/1000;

        /* bytes/s */
        if(sec > 0)
        {
            current_upload_speed = transfer.last_size / sec;
            transfer.last_size = 0;
            transfer.time.restart();
            //qWarning() << "current_upload_speed" << current_upload_speed << "kb/s";
        }

        total_uploaded += transfer._sz;
        transfer.uploaded += transfer._sz;
        txRxSizeRefreshed(transfer._sz, 0);

        if(transfer.uploaded >= (qint64)fsize) {
            qWarning() << "Transfer done" << fsize << " == " << transfer.uploaded;
            if(_socket->isOpen())
                _socket->disconnectFromHost();
        }

        return;
    }


    if(_socket && transfer.fs_obj && transfer.file_fd > -1) {
        if(_socket->isOpen())
            _socket->disconnectFromHost();
    }

    if(disconnecting) {
        if(transfer.fs_obj && transfer.file_fd > -1)
            transfer.fs_obj->fileClose(transfer.file_fd);
        else
            qWarning() << "File not opened, nothing to do";
        transfer.file_fd = -1;
    }
}


void ClientThread::socket_ready_read()
{
    QByteArray barr = _socket->readAll();
    total_downloaded += barr.size();
    txRxSizeRefreshed(0, barr.size());

    QString req = trUtf8(barr), ansver;
    barr.clear();

    qWarning() << " +++ Req +++ \n" << req;

    /* prepare ansver */
    prepareAnsverData(req, ansver, &transfer.fs_obj, &transfer.file_fd);

    /* print request to log */
    serv->emitLog(req.remove(req.length()-2, 2), 0xffff00ff);

    qWarning() << "Ansver \n" << ansver;

    /* writing header */
    transfer._sz = _socket->write(ansver.toUtf8());
    if(transfer._sz == -1) {
        qWarning() << __FUNCTION__ << ": " << __LINE__ << "Writing error";
        goto end;
    }


    total_uploaded += transfer._sz;
    emit txRxSizeRefreshed(transfer._sz, 0);

    qWarning() << " Header writed!";


    /* is file opened, we need to send data */
    if(transfer.fs_obj && transfer.file_fd > -1 && transfer.fs_obj->fileIsOpen(transfer.file_fd) && transfer.fs_obj->fileSize(transfer.file_fd) > 0)
    {
        setDownloadingObject(transfer.fs_obj->name());
        transfer.part_size = 4*1024;
        transfer.uploaded = 0;
        transfer.time.start();

        _socket->flush();
        socket_bytes_write();
        return;
    }

end:

    if(_socket)
        _socket->disconnectFromHost();

}



void ClientThread::run()
{
    transferData();
}


void ClientThread::clientConnected()
{
    qWarning() << __FUNCTION__;
    _ip = _socket->peerAddress().toString();
    qWarning() << "ip: " << _ip;
    emit clientThreadCreated(this);
}




void ClientThread::disconnected_thread(ClientThread *th)
{
    emit th->clientThreadDestroyed(th);
}


void ClientThread::clientDisconnected()
{
    if(disconnect_emited) return;
    disconnect_emited = true;

    qWarning() << __FUNCTION__;
    qWarning() << "Thread  finished!";

    if(transfer.fs_obj && transfer.file_fd > -1)
        transfer.fs_obj->fileClose(transfer.file_fd);
    else
        qWarning() << "File not opened, nothing to do";
    transfer.file_fd = -1;

    //serv->scheduler.schedule((void*)disconnected_thread, (void*)this);
    emit clientThreadDestroyed(this);

    qWarning() << "Ololo";
}


void ClientThread::waitForFinished()
{
    wait();
}


QString ClientThread::ip()
{
    return _ip;
}


QTcpSocket *ClientThread::socket()
{
    return _socket;
}


void ClientThread::dialog()
{
    start();
}

